It's all well and good that we are no longer having to transform vertices manually.
But perhaps we can move more things to the vertex shader. Could it be possible to move
all of ComputePositionOffsets
to the vertex shader?
Well, no. The call to glutGet(GL_ELAPSED_TIME)
cannot be moved
there, since GLSL code cannot directly call C/C++ functions. But everything else can be
moved. This is what vertCalcOffset.cpp
does.
The vertex program is found in data\calcOffset.vert
.
Example 3.6. Offset Computing Vertex Shader
#version 330 layout(location = 0) in vec4 position; uniform float loopDuration; uniform float time; void main() { float timeScale = 3.14159f * 2.0f / loopDuration; float currTime = mod(time, loopDuration); vec4 totalOffset = vec4( cos(currTime * timeScale) * 0.5f, sin(currTime * timeScale) * 0.5f, 0.0f, 0.0f); gl_Position = position + totalOffset; }
This shader takes two uniforms: the duration of the loop and the elapsed time.
In this shader, we use a number of standard GLSL functions, like
mod
, cos
, and sin
. We
saw mix
in the last tutorial. And these are just the tip of the
iceberg; there are a lot of standard GLSL functions
available.
The rendering code looks quite similar to the previous rendering code:
Example 3.7. Rendering with Time
void display() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(theProgram); glUniform1f(elapsedTimeUniform, glutGet(GLUT_ELAPSED_TIME) / 1000.0f); glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(0); glUseProgram(0); glutSwapBuffers(); glutPostRedisplay(); }
This time, we do not need any code to use the elapsed time; we simply pass it unmodified to the shader.
You may be wondering exactly how it is that the loopDuration
uniform gets set. This is done in our shader initialization routine, and it is done only
once:
Example 3.8. Loop Duration Setting
void InitializeProgram() { std::vector<GLuint> shaderList; shaderList.push_back(Framework::LoadShader(GL_VERTEX_SHADER, "calcOffset.vert")); shaderList.push_back(Framework::LoadShader(GL_FRAGMENT_SHADER, "standard.frag")); theProgram = Framework::CreateProgram(shaderList); elapsedTimeUniform = glGetUniformLocation(theProgram, "time"); GLuint loopDurationUnf = glGetUniformLocation(theProgram, "loopDuration"); glUseProgram(theProgram); glUniform1f(loopDurationUnf, 5.0f); glUseProgram(0); }
We get the time uniform as normal with glGetUniformLocation
. For
the loop duration, we get that in a local variable. Then we immediately set the current
program object, set the uniform to a value, and then unset the current program
object.
Program objects, like all objects that contain internal state, will retain their state
unless you explicitly change it. So the value of loopDuration
will be
5.0f in perpetuity; we do not need to set it every frame.